package com.jphotomanager.io;

import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.imageio.ImageIO;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import com.jphotomanager.thread.ImageRotationTask;
import com.jphotomanager.thread.ImageRotationTask.ROTATION;
import com.jphotomanager.thread.PreviewGeneratingTask;
import com.mortennobel.imagescaling.ResampleOp;
import com.openroom.db.DBHelper;
import com.openroom.editing.JPEGImageProcessor;
import com.openroom.event.ApplicationEvent;
import com.openroom.event.EventHandler;
import com.openroom.event.EventManager;
import com.openroom.event.EventType;
import com.openroom.model.BackupMaster;
import com.openroom.model.ImageHolder;
import com.openroom.model.ModelLocator;
import com.openroom.model.Photograph;

public class ImageLibrary {

    private static final Logger LOGGER = Logger.getLogger(ImageLibrary.class);

    private static ImageLibrary instance = new ImageLibrary();

    private ExecutorService executorService = null;

    private Photograph image;

    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock originalrwl = new ReentrantReadWriteLock();
    public final Lock pr = rwl.readLock();
    public final Lock pw = rwl.writeLock();
    public final Lock ow = originalrwl.writeLock();

    private BufferedImage previewImage;

    private ImageLibrary() {
        executorService = Executors.newCachedThreadPool();
        EventManager.getInstance().listenToEventType(
                EventType.CURRENT_IMAGE_CHANGED, new EventHandler() {
                    @Override
                    public void handle(ApplicationEvent event) {
                        previewImage = null;
                    }
                });
    }

    public void shutdown() {
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static ImageLibrary getInstance() {
        return instance;
    }

    public BufferedImage getDisplayImage() {
        pr.lock();
        image = getcurrentImage();
        try {
            File rawFile = new File(image.getRawFilePath());
            File jpegFileName = RawConverter.getJpegFileNameForRawFile(rawFile);
            if (!jpegFileName.exists()
                    && RawConverter.isSupportedRawFile(rawFile))
                RawConverter.extractJpeg(rawFile);
            if (image.getPreviewImagePath() == null) {
                try {
                    BufferedImage originalImage = ImageIO.read(jpegFileName);
                    if (originalImage == null)
                        return ImageIO.read(new File(image.getThumbnailPath()));
                    executorService.execute(new PreviewGeneratingTask(
                            originalImage, image));
                    return originalImage;
                } catch (Exception e) {
                    LOGGER.error("error", e);
                    return ImageIO.read(new File(image.getThumbnailPath()));
                }
            } else {
                previewImage = ImageIO.read(new File(image
                        .getPreviewImagePath()));
                return previewImage;
            }
        } catch (IOException e) {
            LOGGER.error("Can not load image " + image);
            LOGGER.error("error", e);
        } finally {
            pr.unlock();
        }
        return null;
    }

    public BufferedImage crop(Rectangle cropRect) {
        try {
            ow.lock();
            BufferedImage displayImage = ImageIO.read(new File(image
                    .getRawFilePath()));
            LOGGER.info(cropRect + "/" + displayImage.getWidth());
            BufferedImage croppedImage = displayImage.getSubimage(cropRect.x,
                    cropRect.y, cropRect.width, cropRect.height);
            BufferedImage previewImage = createScaledImage(croppedImage,
                    ImageImporter.PREVIEW_WIDTH, ImageImporter.PREVIEW_HEIGHT);
            BufferedImage tnailImage = createScaledImage(previewImage,
                    ImageImporter.PREVIEW_WIDTH, ImageImporter.PREVIEW_HEIGHT);
            updateCurrentImage(image, croppedImage, previewImage, tnailImage);
            return previewImage;
        } catch (IOException e) {
            LOGGER.error("error", e);
        } finally {
            ow.unlock();
        }
        return null;
    }

    public static void updateCurrentImage(Photograph photograph,
            BufferedImage originalImage, BufferedImage previewImage,
            BufferedImage tnailImage) {
        LOGGER.debug("ModelLocator.updateCurrentImage()");
        BackupMaster backup = DBHelper.getInstance().getBackup(photograph);
        Long backupId = null;
        if (backup == null) {
            backupId = DBHelper.getInstance().creatBackupMaster(
                    photograph.getPhotoId());
        } else {
            backupId = backup.getBackupId();
            // If a backup is already there then delete current files
            if (originalImage != null)
                FileUtils.deleteQuietly(new File(photograph.getRawFilePath()));
            if (previewImage != null)
                FileUtils.deleteQuietly(new File(photograph
                        .getPreviewImagePath()));
            if (tnailImage != null)
                FileUtils
                        .deleteQuietly(new File(photograph.getThumbnailPath()));
        }
        File newImagePath = new File(photograph.getRawFilePath());
        if (originalImage != null) {
            newImagePath = ImageImporter.saveEditedImage(originalImage,
                    photograph);
            LOGGER.debug("ModelLocator.updateCurrentImage()");
            LOGGER.debug(newImagePath);
        }
        File previewPath = new File(photograph.getPreviewImagePath());
        if (previewImage != null) {
            previewPath = ImageImporter.saveEditedImage(previewImage,
                    photograph);
            LOGGER.debug("Saving " + previewPath);
        }
        File tnail = new File(photograph.getThumbnailPath());
        if (tnailImage != null) {
            tnail = ImageImporter.saveEditedImage(tnailImage, photograph);
        }

        DBHelper.getInstance().updatePreviewPath(photograph.getPhotoId(),
                previewPath.getAbsolutePath(), newImagePath.getAbsolutePath(),
                tnail.getAbsolutePath(), backupId);
        ModelLocator instance = ModelLocator.getInstance();
        int currentIndex = instance.getAllPhotographsOfFolder().indexOf(
                photograph);
        instance.setImageProcessor(null);
        if (currentIndex == -1)
            return;
        photograph.setMasterId(backupId);
        Photograph photograph2 = instance.getAllPhotographsOfFolder().get(
                currentIndex);
        photograph2.setPreviewImagePath(previewPath.getAbsolutePath());
        photograph2.setRawFilePath(newImagePath.getAbsolutePath());
        photograph2.setThumbnailPath(tnail.getAbsolutePath());
        photograph2.setMasterId(backupId);

    }

    public void revertToOriginal() {
        Photograph original = DBHelper.getInstance().restoreOriginal(
                ModelLocator.getInstance().getCurrentlySelectedImage()
                        .getPhotoId());
        ModelLocator
                .getInstance()
                .getAllPhotographsOfFolder()
                .set(ModelLocator.getInstance().getCurrentlySelectedIndex(),
                        original);
        ModelLocator.getInstance().setCurrentlySelectedImage(original);
    }

    public ImageHolder fixWB() {
        ModelLocator instance = ModelLocator.getInstance();
        JPEGImageProcessor ip2 = instance.getOrLoadImagePreviewProcessor();
        ip2.resetRoi();
        ip2.autoAdjustContrast();
        BufferedImage createImage = ip2.createImage();
        BufferedImage tnailImage = createScaledImage(createImage,
                ImageImporter.THUMBNAIL_WIDTH, ImageImporter.THUMBNAIL_HEIGHT);

        ImageHolder imageHolder = new ImageHolder();
        imageHolder.setPreviewImage(createImage);
        imageHolder.setThumbnailImage(tnailImage);
        ModelLocator.getInstance().setCurrentImageHolder(imageHolder);
        ModelLocator.getInstance().setImageProcessor(ip2);
        ModelLocator.getInstance().updateCurrentImage();
        return imageHolder;
    }
    
    public BufferedImage rotateLeft() {
        return rotate(ROTATION.LEFT);
    }

    public BufferedImage rotateRight() {
        return rotate(ROTATION.RIGHT);
    }

    public BufferedImage flipHorizontal() {
        return rotate(ROTATION.FLIP_HORIZONTAL);
    }

    public BufferedImage flipVertical() {
        return rotate(ROTATION.FLIP_VERTICAL);
    }

    private BufferedImage rotate(ImageRotationTask.ROTATION rotation) {
        try {
            System.out.println("ImageLibrary.rotate()" + rotation);
            if (previewImage == null)
                previewImage = getDisplayImage();
            pw.lock();
            JPEGImageProcessor ip = JPEGImageProcessor
                    .createImageProcessor(previewImage);
            switch (rotation) {
            case LEFT:
                ip = ip.rotateLeft();
                break;
            case RIGHT:
                ip = ip.rotateRight();
                break;
            case FLIP_HORIZONTAL:
                ip = ip.flipHorizontal();
                break;
            case FLIP_VERTICAL:
                ip = ip.flipVertical();
                break;
            default:
                break;
            }
            BufferedImage createImage = ip.createImage();
            previewImage = null;
            previewImage = createImage;
            executorService.execute(new ImageRotationTask(image, rotation));
            return createImage;
        } finally {
            pw.unlock();
        }
    }

    private Photograph getcurrentImage() {
        Photograph currentImage = ModelLocator.getInstance()
                .getCurrentlySelectedImage();
        return currentImage;
    }

    public BufferedImage createScaledImage(BufferedImage read, int destWidth,
            int destHeight) {
        int origWidth = read.getWidth();
        int origHeight = read.getHeight();
        float aspectRatio = ((float) origWidth / origHeight);
        int scaledWidth = origWidth;
        int scaledHeight = origHeight;
        if (aspectRatio < 1) {
            scaledHeight = destHeight;
            scaledWidth = (int) (aspectRatio * destHeight);
        } else {
            scaledWidth = destWidth;
            scaledHeight = (int) (destWidth / aspectRatio);
        }
        ResampleOp resampleOp = new ResampleOp(scaledWidth, scaledHeight);
        BufferedImage rescaledImage = resampleOp.filter(read, null);
        return rescaledImage;
    }

    public BufferedImage getPreviewImage() {
        return previewImage;
    }

    public void setExposure(int exposureCompValue) {
        JPEGImageProcessor ip = ModelLocator.getInstance()
                .getOrLoadImagePreviewProcessor();
        ip.increaseExposureBy(exposureCompValue);
        BufferedImage createImage = ip.createImage();
        previewImage = createImage;
    }

    public void setShadows(int exposureCompValue) {
        JPEGImageProcessor ip = ModelLocator.getInstance()
                .getOrLoadImagePreviewProcessor();
        ip.increaseShadowsBy(exposureCompValue);
        BufferedImage createImage = ip.createImage();
        previewImage = createImage;
    }
    
    public void adjustBrightness(int value)
    {
        JPEGImageProcessor ip = ModelLocator.getInstance()
                .getOrLoadImagePreviewProcessor();
        ip.adjustBrightness(value);
        BufferedImage createImage = ip.createImage();
        previewImage = createImage;
    }

    public void increaseSaturation(int value){
        JPEGImageProcessor ip = ModelLocator.getInstance()
                .getOrLoadImagePreviewProcessor();
        ip.setSaturation((float)value/20);
        ip.apply();
        BufferedImage createImage = ip.createImage();
        previewImage = createImage;
    }
    public ExecutorService getExecutorService() {
        return executorService;
    }

    public void setContrast(int value) {
        JPEGImageProcessor ip = ModelLocator.getInstance()
                .getOrLoadImagePreviewProcessor();
        ip.setContrast(((float)value/20));
        ip.apply();
        BufferedImage createImage = ip.createImage();
        previewImage = createImage;
    }
}
